<?php

/*
** Content creation
*/
function buffer_start() {
  ob_start();
}

function buffer_collect(&$out) {
  $out .= ob_get_contents();
  ob_end_clean();
}


/** @var string - redirect type permanent */
define('REDIRECT_PERMANENT', 'permanent');
/** @var string - redirect type temporary */
define('REDIRECT_TEMPORARY', 'temporary');
define('REDIRECT_FOUND', 'found');
/**
 * Redirects page and exits script if $exit is TRUE.
 * @param <type> $url
 * @param <type> $exit
 * @param <type> $redirectType
 */
function RedirectPage($url, $exit = true, $redirectType = REDIRECT_FOUND)
{
  $redirectCodes = array(
    REDIRECT_PERMANENT => 301,
    REDIRECT_TEMPORARY => 307,
    REDIRECT_FOUND => 302, // defualt value in php's header implementation
  );
  $redirectCode = $redirectCodes[$redirectType];
  
  if (intval($redirectCode) <= 0)
    $redirectCode = 302;
  header('Location: ' . $url, null, $redirectCode);
  if ($exit == true)
    exit(0);
}

// Returns the local (server) URL of the current page
function GetPageLocalURL()
{
  return $_SERVER["REQUEST_URI"];
}

// Returns the URL of the current page relative to the base folder of the site
function GetPageRelativeURL()
{
  $url = GetPageLocalURL();
  if (StringStartsWith($url, GetBaseFolder()))
    $url = substr($url, strlen(GetBaseFolder()) - 1);
  return $url;
}

function GetBaseFolder()
{
  global $BASE_FOLDER;
  return $BASE_FOLDER;
}

/*
** Cryptography
*/

function EncryptPassword($text)
{
  return hash('md5', $text);
}

function EncryptDES($text, $key)
{
  $iv_size = 8;
  $iv = 'g%f-40j#';
  return base64_encode(mcrypt_encrypt(MCRYPT_DES, $key, $text, MCRYPT_MODE_CFB, $iv));
}

function DecryptDES($text, $key)
{
  $iv_size = 8;
  $iv = 'g%f-40j#';
  return mcrypt_decrypt(MCRYPT_DES, $key, base64_decode($text), MCRYPT_MODE_CFB, $iv);
}

function Encrypt($text)
{
  return EncryptDES($text, '^t$,2xu)');
}

function Decrypt($text)
{
  return DecryptDES($text, '^t$,2xu)');
}

/*
** Strings
*/

function StringStartsWith(&$string, $substring)
{
  return (strncmp($string, $substring, strlen($substring)) == 0);
}

function StringEndsWith(&$string, $substring)
{
  $strlen = strlen($string);
  $substrlen = strlen($substring);
  if ($substrlen > $strlen)
    return false;
  return (substr_compare($string, $substring, -$substrlen) === 0);
}


/*
** Misc
*/

// Divides 2 integer numbers. If the divider is 0, then returns 0.
function Divide($a, $b)
{
  if ($b == 0) return 0; else return ($a - ($a % $b)) / $b;
}

// Returns Date object represented by a YYYY-MM-DD string
function StrToDate($strDate)
{
  $parts = split('-', $strDate);
  if (count($parts) != 3) return mktime(0,0,0,0,0,0);
  $y = intval($parts[0]);
  $m = intval($parts[1]);
  $d = intval($parts[2]);
  return mktime(0,0,0,$m,$d,$y);
}

/**
 * Formats timestamp to a javascript compatible string representation without the
 *    timezone specification so that the browser considers it a local date.
 * @param integer $timestamp
 * @param <type> $timestamp
 */
function FormatJavascriptLocaleDate($timestamp)
{
  // (NOT COMPATIBLE WITH IE8) ISO 8601 date without timestamp (2004-02-12T15:19:21+00:00)
  //  return date('o-m-d\TH:i:s', $timestamp);

  // RFC 822: 06 Nov 1994 (derived from: Sun, 06 Nov 1994 08:49:37 GMT)
  return date('j M Y', $timestamp) . ' 00:00:00 GMT +0000';
}

function SendeMail($to, $subject, $body, $fromName, $fromEmail, $attachments = null)
{
  // supress all exceptions and errors from phpmailer
  try
  {
    $mail = new PHPMailer(true);
    $mail->IsSMTP();                    // set mailer to use SMTP
    $mail->CharSet = 'UTF-8';

    $mail->SMTPAuth = true;
    $mail->Host = SMTP_SERVER;
    $mail->Username = SMTP_USERNAME;        // SMTP username
    $mail->Password = SMTP_PASSWORD;             // SMTP password
    $mail->Port = SMTP_PORT;
    $mail->SMTPSecure = SMTP_SECURE;

    if (stripos($to, ',') === false)
      $mail->AddAddress($to);
    else
    {
      $recipients = explode(',', $to);
      foreach ($recipients as $recipient)
        $mail->AddAddress(trim($recipient));
    } 
    $mail->From = $fromEmail;
    $mail->FromName = $fromName;
    $mail->Subject = $subject;

    $mail->IsHTML(true);                // set email format to HTML
    $mail->Body = $body;
    $mail->AltBody = null;

    if (is_array($attachments))
    {
      foreach ($attachments as $attachment)
      {
        $mail->AddAttachment($attachment['path'], $attachment['name']);
      }
    }

    if (IS_DEVELOPMENT_SERVER)
    {
      $file = fopen('files/email-log.html', 'a+');
      $text = "<br/><h2>to: $to</h2> \n <h2>from: $fromName email: $fromEmail></h2> \n <h2>subject: $subject</h2> \n <h2>body:</h2> $body<br/>\n"
            . "<hr/>\n";
      fwrite($file, $text);
      fclose($file);
    }
    else
    {
      $mail->Send();
    }
  }
  catch (Exception $e)
  {

  }
}

function TimeToSeconds($time)
{
  $tm_hours = (int)substr($time, 0, 2);
  $tm_min = (int)substr($time, 3, 2);
  $tm_sec = (int)substr($time, 6, 2);
  return $tm_sec + $tm_min * 60 + $tm_hours * 3600;
}

/**
 * Checks whether a string is a machine readable internal name ([a-zA-Z_] chars only).
 * @param string $name
 * @return <type> 
 */
function ValidInternalName($name)
{
  return preg_match('/^[0-9a-zA-Z_-]+$/', $name) === 1;
}
/**
 * Check that $id is a number greater that zero.
 * @param integer $id
 */
function ValidId($id)
{
  return (is_numeric($id) && intval($id) > 0);
}

/**
 * Checks that a string is empty.
 */
function StringIsEmpty(&$string)
{
  return ($string === "" || $string === null);
}

/**
 * Copies file fron $src to $dst and creates $dst's folder if it doesn't exist.
 */
function CopyFile($src, $dst)
{
  $dstDir = dirname($dst);
  if (!file_exists($dstDir))
    mkdir ($dstDir, 0775, true);
  return copy($src, $dst);
}

/**
 * Moves file fron $src to $dst and creates $dst's folder if it doesn't exist.
 */
function MoveFile($src, $dst)
{
  $dstDir = dirname($dst);
  if (!file_exists($dstDir))
    mkdir ($dstDir, 0775, true);
  if (file_exists($dst))
    unlink($dst);
  return rename($src, $dst);
}

/**
 * Creates folder with 0755 permissions if it doesnt exist.
 */
function CreateFolder($dir)
{
  if (!file_exists($dir))
    mkdir ($dir, 0775, true);
}

/**
 * Deletes file or folder (recursively).
 * @param <type> $dir 
 */
function DeleteFile($dir)
{
  if (is_dir($dir))
  {
    $objects = scandir($dir);
    foreach ($objects as $object)
    {
      if ($object != "." && $object != "..")
      {
        if (filetype($dir . "/" . $object) == "dir")
          DeleteFile($dir . "/" . $object);
        else
          unlink($dir."/".$object);
      }
    }
    reset($objects);
    rmdir($dir);
  }
  else
    unlink($dir);
}


// debug function with multiple arguments and backtrace
function debug()
{
  global $DEBUG_OUT;
  $visible = true;

  $containers = array(
    'head' => '<p style="text-align:left;">',
    'foot' => '</p>'
  );

  $numargs = func_num_args();
  $arg_list = func_get_args();
  // add backtrace

  $DEBUG_OUT .= '<hr/><h3>Debug:</h3>';
  for ($i = 0; $i < $numargs; $i++) {
    $var = &$arg_list[$i];

    $r = var_export($var, true);
    $r = str_replace(array(' ', "\n"), array('&nbsp;', "<br />\n"), $r);

    $r = $containers['head'] . '<b>variable '.$i.':</b> '. $r . $containers['foot'];
    $DEBUG_OUT .= $r;
  }
  $backtrace = debug_backtrace();
  $var = &$backtrace[1];

  $r = var_export($var, true);
  $r = str_replace(array(' ', "\n"), array('&nbsp;', "<br />\n"), $r);

  $r = $containers['head'] . '<b>backtrace:</b> '. $r . $containers['foot'];
  $DEBUG_OUT .= $r;

  $DEBUG_OUT .= '<hr/>';
}

// debug function with multiple arguments and backtrace
function print_var()
{
  global $DEBUG_OUT;
  $visible = true;

  $containers = array(
    'head' => '<p style="text-align:left;">',
    'foot' => '</p>'
  );
   
  $numargs = func_num_args();
  $arg_list = func_get_args();
  // add backtrace

  $DEBUG_OUT .= '<hr/><h3>Debug:</h3>';
  for ($i = 0; $i < $numargs; $i++) {
    $var = &$arg_list[$i];
    
    $r = var_export($var, true);
    $r = str_replace(array(' ', "\n"), array('&nbsp;', "<br />\n"), $r);
 
    $r = $containers['head'] . '<b>variable '.$i.':</b> '. $r . $containers['foot'];
    $DEBUG_OUT .= $r;
  }

  $r = str_replace(array(' ', "\n"), array('&nbsp;', "<br />\n"), $r);

  $r = $containers['head'] . $containers['foot'];
  $DEBUG_OUT .= $r;
  
  $DEBUG_OUT .= '<hr/>';
}

function LogError($msg, $type = 'error')
{
  $backtrace = debug_backtrace();
  $var = &$backtrace[1];
  $msg = $var['function']. ":" . $var['line'] . " - " . $msg;

  $logger = GetLogger(LOGS_FOLDER . 'errors.log');
  $logger->LogError($msg);
  
  global $ERRORS;
  if (!is_array($ERRORS))
    $ERRORS = array();
  $ERRORS[] = array($type, $msg);
}

function GetErrors($type = 'error')
{
  global $ERRORS;
  $out = "";
  foreach ((array)$ERRORS as $error)
  {
    if (!empty($type) && $type != 'all')
    {
      if ($error[0] !== $type)
        continue;
      $out .= strtoupper($error[0]). ": " . $error[1] . "<br/>";
    }
    else
      $out .= strtoupper($error[0]). ": " . $error[1] . "<br/>";
  }
  return $out;
}


function LogErrorToFile($msg, $filename = null)
{
  if (is_null($filename))
    $filename = GENERIC_LOG_FILE;
  $file = fopen($filename, 'a+');

  $str = "[".date('Y-m-d H:i:s').'] '.$msg;
  fwrite($file, $str."\n");
  fclose($file);
}

function LogRecursionError($recursionIndex)
{
  $backtrace = debug_backtrace();
  $strError = "Max level of $recursionIndex surpassed. \n" 
              . var_export($backtrace[1], true);
  LogErrorToFile($strError,  LOGS_FOLDER .  'infinite-recursions.txt');
}

/**
 * Generate full text search string
 * @param String $search - search words
 * @param Boolean $fullWords - TRUE to search for only full matches (no wildcards)
 * @param Boolean $redundantWords - TRUE to also search for fts redundant words
 *        (under 4 chars length). 
 * @return <type>
 *
 * @see function AddRedundantFtsWords
 */
function PrepareFtsPhrase($search, $fullWords = false, $redundantWords = false)
{
  $stopchars = array('\\', '\n', '\r');
  $search = mysql_real_escape_string($search);
  $search = str_replace($stopchars, ' ', ' ' . $search . ' ');
  $search = str_replace('  ', ' ', $search);
  $search = trim($search);
  $stopwordsRO = array();
  $stopwordsRU = array();
  $stopwordsEN = array(" a "," an "," and "," or "," the "," it "," i ");
  $stopwords = array_merge($stopwordsRO, $stopwordsRU, $stopwordsEN);

  $search = str_replace($stopwords, " ", $search);
  $indexArray = explode(" ", $search);
  // Strip double words
  $indexArray = array_unique($indexArray);
  $full_text_search = array();
  foreach ($indexArray as $word)
  {
    $word = trim($word);
    if (strlen($word) > 0)
    {
      $strlen = mb_strlen($word, 'utf-8');
      if ($strlen > 3)
      {
        if ($fullWords === true)
          $full_text_search[] = '+' . $word;
        else
          $full_text_search[] = '+' . $word . '*';
      }
      else if ($redundantWords === true)
      {
        if ($fullWords === true)
          $full_text_search[] = '+' . GenerateRedundantFtsWord($word);
        else
          $full_text_search[] = '+(' . GenerateRedundantFtsWord($word) . ' ' . $word . '*' . ')';
      }
    }
  }
  return implode(' ', $full_text_search);
}

/**
 * Adds fts redundant words - words that facilitate a better search:
 *      1. small words (3 chars max) are duplicated as ZZZ<word>ZZZ to hack the
 *          ft_min_word_len of 4 chars restriction
 * @param String $fts
 * @return String $fts
 */
function AddRedundantFtsWords($fts)
{
  // remove duplicates
  $words = explode(" ", $fts);
  foreach ($words as &$word)
  {
    $word = trim($word);
    $strlen = mb_strlen($word, 'utf-8');
    if ($strlen < 4 && $strlen > 0)
      $word = $word . " " . GenerateRedundantFtsWord($word);
  }
  $fts = implode(' ', $words);
  return $fts;
}

/**
 * Removes redundant fts words from fts field
 * @param <type> $strTerms
 * @param <type> $lowercase
 * @return <type> 
 */
function RemoveRedundantFtsWords($strTerms, $lowercase = false)
{
  if ($lowercase === true)
    $words = explode('zzz', $strTerms);
  else
    $words = explode('ZZZ', $strTerms);
  return implode($glue, $words);
}

/**
 * Removes unwanted chars from fts field.
 * @param <type> $ftsString
 * @return <type> 
 */
function RemoveIgnoredFtsChars($ftsString)
{
  $chars = array('™', '®');
  return str_replace($chars, ' ', $ftsString);
}

/**
 * @see function AddRedundantFtsWords
 */
function IsRedundantFtsWord($word)
{
  return mb_strlen($word, 'utf-8') >= 6 && StringStartsWith($word, 'ZZZ') && StringEndsWith($word, 'ZZZ');
}

/**
 * @see function AddRedundantFtsWords
 */
function GenerateRedundantFtsWord($word)
{
  return "ZZZ{$word}ZZZ";
}

/**
 * removes duplicate words from a string
 * @param <type> $str
 * @return <type> 
 */
function RemoveDuplicateWords($str)
{
  // remove duplicates
  $words = explode(' ', trim($str));
  $str = '';
  $words = array_unique($words);
  $str = implode(' ', $words);
  return $str;
}

/**
 * Extracts terms (words) from a string in two modes: maintan term sequence while
 *          not removing redundat term, removing redundant terms while not always
 *          maintaning term sequence.
 *
 * @param <type> $strTerms
 * @param Boolean $unique - TRUE to extract unique terms with order not respected.
 * @param Boolean $splitOnDash - TRUE to split and duplicate terms on dash
 * @return <type> 
 */
function &ExtractStringTerms($strTerms, $unique = true, $splitOnDash = true)
{
  $strTerms = str_replace(array('"', '\'', '/', '\\', ';', ',', '(', ')', '[', ']', '{', '}', '+', '|', '*'), " ", $strTerms);
  $terms = explode(" ", $strTerms);
  /** @var dictionary of unique terms */
  $outTermsUnique = array();
  /** @var array of terms with their sequence maintained */
  $outTerms = array();
  /** @var array - appended terms when maintaning term sequence */
  $appendedTerms = array();
  foreach ($terms as &$term)
  {
    $term = trim($term);
    if (!$term)
      continue;

    $termUpper = mb_strtoupper($term, 'utf-8');
    // if $unique dont add redundant terms
    if ($unique && array_key_exists($termUpper, $outTermsUnique))
      continue;
    // some products have composite ids separated by a dash, so split them.
    if ($splitOnDash === true && strpos($term, "-") !== false)
    {
      $parts = explode("-", $term);
      foreach($parts as &$part)
      {
        $part = trim($part);
        $partUpper = mb_strtoupper($part, 'utf-8');
        if ($partUpper)
        {
          if ($unique)
            $outTermsUnique[$partUpper] = $part;
          else
            $appendedTerms[$partUpper] = $part;
        }
      }
    }
    if ($unique)
      $outTermsUnique[$termUpper] = $term;
    else
      $outTerms[] = $term;
  }
  if ($unique)
    return array_values($outTermsUnique);
  else
  {
    return array_merge($outTerms, $appendedTerms);
  }
}

function ConvertCurrency($from, $to, $value)
{
  static $currencies;
  if (!is_array($currencies))
  {
    $query = "SELECT name, rate FROM currencies";
    $currencies = ExecuteQuery($query);
  }

  $ratioFrom = 1;
  $ratioTo = 1;
  for ($i = 0, $count = count($currencies); $i < $count; $i++)
  {
    $currency = & $currencies[$i];
    if ($from == $currency['name'])
      $ratioFrom = $currency['rate'];
    else if ($to == $currency['name'])
      $ratioTo = $currency['rate'];
  }
  $value = $value * $ratioFrom / $ratioTo;
  return $value;
}

/**
 * Converts value to given $currency (or user's current currency if none given)
 *      and formats the value.
 * @param Float $amount -
 * @param Boolean $format - format
 *        '%i' - amount, INTEGER value
 *        '%d' - amount, DECIMAL value with 2 digits after the point
 *        '%c' - currency CODE representation (MDL, USD)
 *        '%n' - currency NAME (Lei, Dollars, Euro)
 *        '%s' - currency SYMBOl ($, Lei)
 * @param String $currency - currency to convert to.
 */
function FormatMoney($amount, $format = null, $currency = null)
{
  if (!$currency)
    $currency = $GLOBALS['session']->currency;
  if (!$format)
    $format = DEFAULT_MONEY_FORMAT;
  $amount = ConvertCurrency(CURRENCY_MDL, $currency, $amount);
  $i = round($amount, 0);
  $d = round($amount, 2);
  $replacePairs = array(
    '%i' => $i,
    '%d' => $d,
    '%c' => t($currency),
    '%s' => t($currency . '-symbol'),
    '%n' => t('value-' . $currency . '-name'),
  );
  return strtr($format, $replacePairs);
}


/**
 * Extracts filename body (i.e. name without extension) from any path.
 * @param String $filepath
 * @return String - filenam without extension
 */
function GetFilenameBody($filepath)
{
  $info = pathinfo($filepath);
  return basename($filepath, '.' . $info['extension']);
}

/**
 * Extracts filename from any path.
 * @param String $filepath
 * @return String - filenam without extension
 */
function GetFilename($filepath)
{
  return basename($filepath);
}

/**
 * Extracts filename body (i.e. name without extension) from any path.
 * @param String $filepath
 * @return String - filenam without extension
 */
function GetFilenameExtension($filepath)
{
  $info = pathinfo($filepath);
  return $info['extension'];
}

/**
 * Gets an instantion of Klogger for given $file and $priority
 * @global KLogger $LOGGERS
 * @param <type> $file
 * @param <type> $level
 * @return KLogger 
 */
function GetLogger($file, $level = KLogger::DEBUG)
{
  if (!$file || !$level)
    return new KLogger(null, KLogger::OFF);
  
  if (!file_exists(dirname($file)))
    mkdir(dirname($file), 0755, true);
  global $LOGGERS;
  $key = $file . '_' . $level;
  if (!array_key_exists($key, $LOGGERS))
    $LOGGERS[$key] = new KLogger($file, $level);
  return $LOGGERS[$key];
}


function IsBrowser()
{
  // taken from browser.php class (http://chrisschuld.com/projects/browser-php-detecting-a-users-browser-from-php/)
  $browsers = array(
    'Opera',                            // http://www.opera.com/
		'Opera Mini',                  // http://www.opera.com/mini/
		'WebTV',                            // http://www.webtv.net/pc/
		'Internet Explorer',                   // http://www.microsoft.com/ie/
		'Pocket Internet Explorer',     // http://en.wikipedia.org/wiki/Internet_Explorer_Mobile
		'Konqueror',                    // http://www.konqueror.org/
		'iCab',                              // http://www.icab.de/
		'OmniWeb',                        // http://www.omnigroup.com/applications/omniweb/
		'Firebird',                      // http://www.ibphoenix.com/
		'Firefox',                        // http://www.mozilla.com/en-US/firefox/firefox.html
		'Iceweasel',                    // http://www.geticeweasel.org/
		'Shiretoko',                    // http://wiki.mozilla.org/Projects/shiretoko
		'Mozilla',                        // http://www.mozilla.com/en-US/
		'Amaya',                            // http://www.w3.org/Amaya/
		'Lynx',                              // http://en.wikipedia.org/wiki/Lynx
		'Safari',                          // http://apple.com
		'iPhone',                          // http://apple.com
		'iPod',                              // http://apple.com
		'iPad',                              // http://apple.com
		'Chrome',                          // http://www.google.com/chrome
		'Android',                        // http://www.android.com/
		'BlackBerry',                  // http://www.blackberry.com/
		'IceCat',                          // http://en.wikipedia.org/wiki/GNU_IceCat
		'Nokia S60 OSS Browser',        // http://en.wikipedia.org/wiki/Web_Browser_for_S60
		'Nokia Browser',                    // * all other WAP-based browsers on the Nokia Platform
		'MSN Browser'                        // http://explorer.msn.com/
  );
  foreach ($browsers as $browser)
    if (stripos($_SERVER['HTTP_USER_AGENT'], $browser))
      return true;

  return false;

}
/**
 * Sets cookie value by also encrypting cookie name
 * @param string $name
 * @param string $value
 * @param boolean $encryptName - TRUE to encrypt cookie name
 * @return type 
 */
function SetCookieValue($name, $value, $expires = null, $encryptName = true)
{  
  if (!function_exists('setcookie'))
    return false;
  
  if ($encryptName === true)
    $name = md5($name);
  if (!is_numeric($expires))
  {
    // set cookie to expire at session end
    if (@constant('COOKIE_EXPIRATION_TIMEOUT') == 0)
      $expires = 0;
    else
      $expires = time() + @constant('COOKIE_EXPIRATION_TIMEOUT');
  }
  if (IS_DEVELOPMENT_SERVER) // localhost
    return setcookie($name, $value, $expires, '/');
  else
    return setcookie($name, $value, $expires, '/', SESSION_COOKIE_DOMAIN_NAME); // www.dietbet.com
}

/**
 * Gets cookie value.
 * @param string $name
 * @param boolean $encryptName - TRUE to encrypt cookie name
 * @return type 
 */
function GetCookieValue($name, $encryptName = true)
{
  if (!function_exists('setcookie'))
    return false;
  if ($encryptName === true)
    $name = md5($name);
  return @$_COOKIE[$name];
}

/**
 * Deletes cookie value
 * @param string $name
 * @param boolean $encryptName - TRUE to encrypt cookie name
 * @return String 
 */
function DeleteCookieValue($name, $encryptName = true)
{
  if (!function_exists('setcookie'))
    return false;
  if ($encryptName === true)
    $name = md5($name);
    // set cookie to expire at session end
  if (IS_DEVELOPMENT_SERVER) // localhost
    return setcookie($name, null, strtotime('-1 year'), '/');
  else
    return setcookie($name, null, strtotime('-1 year'), '/', SESSION_COOKIE_DOMAIN_NAME);
}

/**
 * Convert all first letters to uppercase with UTF-8 support.
 * @param string $str
 * @return string
 */
function mb_ucwords($str)
{
  return mb_convert_case($str, MB_CASE_TITLE, "UTF-8");
}

/**
 * Convert special characters to HTML entities
 * @param string $string
 * @param integer $quoteStyle - ENT_QUOTES, ENT_NOQUOTES, ENT_COMPAT, ENT_IGNORE
 * @return string
 */
function mb_htmlspecialchars($string, $quoteStyle = ENT_QUOTES)
{
  return htmlspecialchars($string, $quoteStyle, 'UTF-8');
}

/**
 * "Eclipses" the text by cutting it if it exceeds given max length and adding
 *    suffix if available.
 * @param string $string
 * @param <type> $maxLength
 * @param <type> $suffix
 * @return string 
 */
function EllipseText($string, $maxLength, $suffix = '&hellip;')
{
  $length = mb_strlen($string, 'UTF-8');
  if ($length > $maxLength)
  {
    $string = mb_substr($string, 0, $maxLength, 'UTF-8') . $suffix;
  }
  return $string;
}

/**
 * Translits cyrillic and romanian characters.
 * @param string $string
 * @return <type> 
 */
function Translit($string)
{
  // diacritice romane
  $string = strtr_utf8($string, "ĂÂÎȘŞȚŢăâîșşțţ", "AAISSTTaaisstt");

  // Сначала заменяем "односимвольные" фонемы.
  $string = strtr_utf8($string, "абвгдеёзийклмнопрстуфхъыэ_", "abvgdeeziyklmnoprstufhiiei");

  $string = strtr_utf8($string, "АБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЭ_", "ABVGDEEZIYKLMNOPRSTUFHIIEI");
  
  // Затем - "многосимвольные".
  $string = strtr_utf8($string,
    array(
      "ж"=>"zh", "ц"=>"ts", "ч"=>"ch", "ш"=>"sh",

      "щ"=>"shch","ь"=>"i", "ю"=>"yu", "я"=>"ya",

      "Ж"=>"ZH", "Ц"=>"TS", "Ч"=>"CH", "Ш"=>"SH",

      "Щ"=>"SHCH","Ь"=>"I", "Ю"=>"YU", "Я"=>"YA",

      "ї"=>"i", "Ї"=>"Yi", "є"=>"ie", "Є"=>"Ye"
    )
  );

  return $string;
}

/**
 * UTF-8 compatible version of strtr function
 * @param string $str
 * @param mixed $from
 * @param mixed $to
 * @return <type> 
 */
function strtr_utf8($str, $from, $to)
{
  if (is_array($from))
    return strtr($str, $from);
  
  $keys = array();
  $values = array();
  // extract all utf letters
  preg_match_all('/./u', $from, $keys);
  preg_match_all('/./u', $to, $values);
  // create a mapping array
  $mapping = array_combine($keys[0], $values[0]);
  return strtr($str, $mapping);
}


/**
 * Loads model class script (experimental) in a much faster way that require_once
 *        (about 100 times faster).
 * NOTE: be aware that when including the model in the local scope some global
 *          variables from the model script are lost (depending on whether you have
 *          previously defined them or not).
 * @param string $className
 */
function LoadModelClass($className)
{
  if (class_exists($className))
    return;

  // extract variables from the global scope to local scope as references (in
  // case any model uses global variables). This should be save because models
  // should only use existing global variables and not create new ones.
  extract($GLOBALS, EXTR_REFS);
  require_once(MODELS_PATH . $className  . '.class.php');
}


/**
 * Formats date to a given language
 * @param string $format - The format of the outputted date string (@see php's
 *        date() function).
 *        Available formats for translation are:
 *            F -	A full textual representation of a month e.g. Ianuarie
 *            M -	A short textual representation of a month, three letters e.g Ian
 *            l - A full textual representation of the day of the week e.g. Luni
 * @param integer $timestamp
 * @param string $lang
 * @return string
 *
 */
function FormatDate($format, $timestamp = null, $lang = null)
{
  if (!$format)
    return '';
  if (!$lang)
    $lang = $GLOBALS['session']->language;
  if (!$timestamp)
    $timestamp = time();

  // no translation for english
  if ($lang == 'en')
    return date($format, $timestamp);

  $format = preg_replace('/([a-zA-Z])/', '##$1##', $format);
  preg_match_all('/##([a-zA-Z0-9]{1})##/', $format, $formatChars, PREG_PATTERN_ORDER);
  $formatChars = @$formatChars[1];

  $date = date($format, $timestamp);
  preg_match_all('/##([a-zA-Z0-9]+)##/', $date, $dateChars, PREG_PATTERN_ORDER);
  $dateChars = @$dateChars[1];
  $replacePairs = array();

  foreach ($formatChars as $key => $formatChar)
  {
    $dateChar = $dateChars[$key];
    // switch to lowercase (translation strings are all lowercase)
    $dateCharLc = mb_strtolower($dateChar);
    switch ($formatChar)
    {
      case 'F': // long month name
        $replacePairs[$dateChar] = t(GetArticledKey($dateCharLc, 'art', null, $lang), null, null, $lang);
        break;
      case 'M': // long month name
        $replacePairs[$dateChar] = t(GetArticledKey('short-' . $dateCharLc, 'art', null, $lang), null, null, $lang);
        break;
      case 'l': // long month name
        $replacePairs[$dateChar] = t($dateCharLc, null, null, $lang);
        break;
    }
  }
  $date = strtr($date, $replacePairs);
  $date = preg_replace('/##([^#]+)##/', '$1', $date);
  return $date;
}


/**
 * Returns an array of all site's languages.
 * @global type $SITE_LANGUAGES
 * @return String
 */
function GetSiteLanguages()
{
  global $SITE_LANGUAGES;
  return $SITE_LANGUAGES;
}

/**
 * Returns an array of all site's languages uppercase sufixes.
 * @staticvar type $languageSufixes
 * @return Array
 */
function GetSiteLanguageSufixes()
{
  static $languageSufixes = null;
  if (!is_array($languageSufixes))
  {
    $languageSufixes = array();
    foreach (GetSiteLanguages() as $lang)
      $languageSufixes[$lang] = strtoupper($lang);
  }
  return $languageSufixes;
}

/**
 * Returns current user's language.
 * @return String
 */
function GetCurrentLanguage()
{
  return $GLOBALS['session']->language;
}

/**
 * Returns the default site language.
 * @return String
 */
function GetDefaultLanguage()
{
  return constant('DEFAULT_LANGUAGE');
}

/**
 * Returns an array of other site languages besides current one.
 * @return Array
 */
function GetOtherLanguages()
{
  $langs = GetSiteLanguages();
  $currentLang = GetCurrentLanguage();
  $otherLangs = array();
  foreach ($langs as $lang)
    if ($lang != $currentLang)
      $otherLangs[] = $lang;
  return $otherLangs;
}

/**
 * Returns an array of all site currencies
 * @global type $SITE_CURRENCIES
 * @return Array
 */
function GetSiteCurrencies()
{
  global $SITE_CURRENCIES;
  return $SITE_CURRENCIES;
}

/**
 * Returns an array of all site currencies titles.
 * @staticvar type $currencyTitles
 * @return Array
 */
function GetSiteCurrencyTitles()
{
  static $currencyTitles = null;
  if (!is_array($currencyTitles))
  {
    $currencyTitles = array();
    foreach (GetSiteCurrencies() as $currency)
      $currencyTitles[$currency] = t($currency);
  }
  return $currencyTitles;
}

/**
 * Returns user's current currency.
 * @return String
 */
function GetCurrentCurrency()
{
  return $GLOBALS['session']->currency;
}

/**
 * Returns the default site currency.
 * @return String
 */
function GetDefaultCurrency()
{
  return constant('DEFAULT_CURRENCY');
}

/**
 * Returns an array of other site currencies beside the current one.
 * @return Array
 */
function GetOtherCurrencies()
{
  $currencies = GetSiteCurrencies();
  $currentCurrency = GetCurrentCurrency();
  $otherCurrencies = array();
  foreach ($currencies as $currency)
    if ($currency != $currentCurrency)
      $otherCurrencies[] = $currency;
  return $otherCurrencies;
}

?>